﻿using System;
using System.Collections.Generic;
using Less.DAL;
using Less.Core.Model;
using Less.Tools;

namespace Less.SRV
{
    public class SecuritySRV:IReadSecurityService, IWriteSecurityService
    {
        readonly UsersDAL usersDAL;
        readonly RolesDAL rolesDAL;
        readonly UsersRolesDAL usersRolesDAL;
        readonly IdentifiersDAL identifiersDAL;

        public SecuritySRV()
        {
            usersDAL = new UsersDAL();
            rolesDAL = new RolesDAL();
            usersRolesDAL = new UsersRolesDAL();
            identifiersDAL = new IdentifiersDAL();
        }
        public SecuritySRV(string connectionStringName)
        {
            usersDAL = new UsersDAL(new Database(connectionStringName));
            rolesDAL = new RolesDAL(new Database(connectionStringName));
            usersRolesDAL = new UsersRolesDAL(new Database(connectionStringName));
            identifiersDAL = new IdentifiersDAL(new Database(connectionStringName));
        }

        public SecuritySRV(IDatabase database)
        {
            usersDAL = new UsersDAL(database);
            rolesDAL = new RolesDAL(database);
            usersRolesDAL = new UsersRolesDAL(database);
            identifiersDAL = new IdentifiersDAL(database);
        }

        public List<User> GetAllUsers()
        {
            return usersDAL.SelectAll();
        }
        public List<Role> GetAllRoles()
        {
            return rolesDAL.SelectAll();
        }
        public void InsertUser(User user)
        {
            if (SelectByEmail(user.Email) != null || SelectByName(user.UserName) != null )
            {
                throw new RecordAlreadyExistsException(user.Serialize());
            }
            else
            {
                user.CreationDate = DateTime.Now;
                usersDAL.Insert(user);
            }
        }
        public void UpdateUser(User user)
        {
            if ( SelectByEmail(user.Email) == null && SelectByName(user.UserName) == null )
            {
                throw new RecordNotFoundException(user.Serialize());
            }
            else
            {
                usersDAL.Update(user);
            }
        }
        public void DeleteUser(User user)
        {
            usersDAL.Delete(user);
        }
        public User SelectByName(string username)
        {
            return usersDAL.SelectByName(username);
        }
        public string GetPassword(string username, string answer)
        {
            return usersDAL.GetPassword(username,answer);
        }
        public User SelectById(int providerUserKey)
        {
            return usersDAL.SelectById(providerUserKey);
        }
        public User SelectByEmail(string email)
        {
            return usersDAL.SelectByEmail(email);
        }

        public User SelectByIdentifier(string identifier)
        {
            return usersDAL.SelectByIdentifier(identifier);
        }

        public string CreateIdentifier(int userId, DateTime? expiryDate)
        {
            Identifier identifier = new Identifier();
            identifier.UserId = userId;
            identifier.GUID = Guid.NewGuid().ToString();
            identifier.CreationDate = DateTime.Now;
            identifier.ExpiryDate = expiryDate??DateTime.Now.AddDays(1);
            identifier.UsedDate = null;
            identifiersDAL.Insert(identifier);
            return identifier.GUID;
        }

        public Identifier SelectIdentifierByGUID(string identifier)
        {
            return identifiersDAL.SelectByGUID(identifier);
        }

        public void ConsumeUserIdentifier(int id)
        {
            Identifier identifier = identifiersDAL.SelectById(id);
            identifier.UsedDate = DateTime.Now;
            identifiersDAL.Update(identifier);
        }

        public int GetNumberOfUsersOnline(DateTime addMinutes)
        {
            return usersDAL.GetNumberOfUsersOnline(addMinutes);
        }

        public List<User> GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            return usersDAL.SelectPage(pageIndex, pageSize, out totalRecords);
        }
        public bool IsUserInRole(string username, string roleName)
        {
            bool exists = false;
            // OPTI: use an INNER JOIN
            User user = usersDAL.SelectByName(username);
            if ( user != null )
            {
                Role role = rolesDAL.SelectByName(roleName);
                if ( role != null )
                {
                    exists = usersRolesDAL.IsUserInRole(user.Id, role.Id);

                }
            }
            return exists;
        }
        public List<Role> SelectRolesByUserName(string username)
        {
            return rolesDAL.SelectRolesByUserName(username);
        }
        public void InsertRoleByName(string roleName)
        {
            Role role = rolesDAL.SelectByName(roleName);
            if ( role == null )
            {
                role = new Role { Name = roleName };
                rolesDAL.Insert(role);
            }
            else
            {
                throw new RecordAlreadyExistsException(roleName);
            }
        }
        public bool DeleteRoleByName(string roleName)
        {
            Role role = rolesDAL.SelectByName(roleName);
            if ( role != null )
            {
                role = new Role { Name = roleName };
                rolesDAL.Delete(role);
                return true;
            }
            else
            {
                throw new RecordNotFoundException(roleName);
            }
        }

        public void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            // OPTI: use an inner join 
            var rolesDAL = new RolesDAL();
            var usersDAL = new UsersDAL();
            foreach ( string roleName in roleNames )
            {
                Role role = rolesDAL.SelectByName(roleName);
                if ( role != null )
                {
                    foreach ( string username in usernames )
                    {
                        User user = usersDAL.SelectByName(username);
                        if ( user != null )
                        {
                            UserRole userRole = usersRolesDAL.SelectByUserIdRoleId(user.Id, role.Id);
                            usersRolesDAL.Delete(userRole);
                        }
                        else
                        {
                            Log.Warning(Msg.NotFound(username));
                        }
                    }
                }
                else
                {
                    Log.Warning(Msg.NotFound(roleName));
                    ;
                }
            }
        }
        public List<User> SelectUsersByRoleName(string roleName)
        {
            return usersDAL.SelectUsersByRoleName(roleName);
        }

        public void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            // OPTI: use an inner join 
            var rolesDAL = new RolesDAL();
            var usersDAL = new UsersDAL();
            foreach ( string roleName in roleNames )
            {
                Role role = rolesDAL.SelectByName(roleName);
                if ( role != null )
                {
                    foreach ( string username in usernames )
                    {
                        User user = usersDAL.SelectByName(username);
                        if ( user != null )
                        {
                            var userRole = new UserRole { UserId = user.Id, RoleId = role.Id };
                            usersRolesDAL.Insert(userRole);
                        }
                        else
                        {
                            Log.Warning(Msg.NotFound(username));
                        }
                    }
                }
                else
                {
                    Log.Warning(Msg.NotFound(roleName));
                    ;
                }
            }
        }
    }
}